#include <stddef.h>
#include <stdint.h>
#include <ctype.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <libspe2.h>
#include <cbe_mfc.h>
#include <pthread.h>
#include <unistd.h>
#include <math.h>
#include <time.h>
#include "common.h"
#include "Bitmap.h"

extern spe_program_handle_t spu;

// Data structure for running SPE thread
typedef struct ppu_pthread_data {
        spe_context_ptr_t speid;
        pthread_t pthread;
        void *argp;
        int tid QWORD_ALIGNED;
} ppu_pthread_data_t;

// create and run one SPE thread
void *ppu_pthread_function(void *arg) {
        ppu_pthread_data_t *datap = (ppu_pthread_data_t *)arg;
        int rc;
        unsigned int entry = SPE_DEFAULT_ENTRY;

        if ((rc = spe_context_run(datap->speid, &entry, 0, datap->argp,(void *) datap->tid, NULL)) < 0) {
                fprintf (stderr, "Failed spe_context_run(rc=%d, errno=%d, strerror=%s)\n", rc, errno, strerror(errno));
                exit (1);
        }

  pthread_exit(NULL);
}

int PrintHeaderInfo(BITMAPINFO *pptPictureData)
{
	   // Print the bitmap header information
	   fprintf(stderr,"Size = %d\n",(pptPictureData)->bmiHeader.biSize);
	   fprintf(stderr,"Image dimentions = %d x %d\n",(pptPictureData)->bmiHeader.biWidth, (pptPictureData)->bmiHeader.biHeight);
	   fprintf(stderr,"Image Size = %d\n",(pptPictureData)->bmiHeader.biSizeImage);
	   fprintf(stderr,"Number of color planes is %d\n",(pptPictureData)->bmiHeader.biPlanes);
	   fprintf(stderr,"Bits per pixel is %d\n",(pptPictureData)->bmiHeader.biBitCount);
	   fprintf(stderr,"Compression type is %d\n",(pptPictureData)->bmiHeader.biCompression);
	   fprintf(stderr,"Number of colors is %d\n",(pptPictureData)->bmiHeader.biClrUsed);
	   fprintf(stderr,"Number of required colors is %d\n", (pptPictureData)->bmiHeader.biClrImportant);
}

void DoSpeWork(CONTROL_BLOCK * controlBlock)
{
	int i;
	CONTROL_BLOCK cb QWORD_ALIGNED;
	PIXEL originalPixels[MAX_PIXELS] QWORD_ALIGNED; //Holds the original picture
	PIXEL changedPixels[MAX_PIXELS] QWORD_ALIGNED; //Holds the changed picture

	// Get the control block from the ppu via DMA
	// Simulate DMA here
	cb = *controlBlock;

	// Bring in original ppu data
	// Simulate DMA here
	for (i = 0; i< MAX_PIXELS; i++)
	{
		originalPixels[i].red = (*((PIXEL *)cb.originalPixels + i)).red;
	}

	// Manipulate the picture
	for (i = 0; i < MAX_PIXELS; i++)
	{
		//changedPixels[i] =  originalPixels[i]; //Just do a 1:1 mapping for now.
		changedPixels[i].red = originalPixels[i].green;
		changedPixels[i].blue = originalPixels[i].blue;
		changedPixels[i].green = originalPixels[i].red;
	}
	//printf("was: %d, now is %d\n", originalPixels[i-1].red, changedPixels[i-1].red);

	// Send the result back
	// Simulate DMA here
	for (i = 0; i < MAX_PIXELS; i++)
	{
		((PIXEL *)cb.changedPixels)[i] = changedPixels[i];
	}
}

char myNum2[5] QWORD_ALIGNED ; // = (int *)malloc(5 * sizeof(int));

int main(int argc,char **argv)
{
   int i, rc;
   /* Check arguments */
   if (argc < 2) {
      fprintf(stderr,"Usage: %s filename\n",argv[0]);
      exit(-1);
   }

   for (i = 0 ; i < 5; i++)
   {
	   myNum2[i] = 'a';
   }

   BITMAPINFO pictureData;
   BITMAPINFO *ptPictureData = &pictureData;

   char * originalPic = LoadDIBitmap(argv[1], &ptPictureData);
   originalPic = originalPic - 1; //Shift the data one char to the left.  Not sure why it's required to keep the data correct.
   PrintHeaderInfo(ptPictureData);

   // make the data more understandable
   int width = (ptPictureData)->bmiHeader.biWidth;
   int height = (ptPictureData)->bmiHeader.biHeight;
   int length = (width * 3 + 3) & ~3;

   int numPixels = (ptPictureData)->bmiHeader.biSizeImage / 3;

   //PIXEL * originalPixels = (PIXEL *)memalign(16, numPixels * sizeof(PIXEL));
   PIXEL originalPixels[numPixels] QWORD_ALIGNED;
   PIXEL * changedPixels = (PIXEL *)memalign(16, numPixels * sizeof(PIXEL));

   char * tmp = originalPic;
   for (i=0; i < numPixels; i++)
   {
	   originalPixels[i].red = tmp[0];
	   originalPixels[i].green = tmp[1];
	   originalPixels[i].blue = tmp[2];
	   tmp += 3;
   }

   //Setup the control block for the spe
   int numThreads = (numPixels / MAX_PIXELS) + 1;
   numThreads = 1;

   CONTROL_BLOCK cb[numThreads] QWORD_ALIGNED;
   ppu_pthread_data_t data[numThreads];

   for(i=0;i<numThreads;i++)
   {
		   data[i].tid = i; //thread id
		   data[i].argp = (unsigned long long *) &cb[i];

           // create SPE context and load SPE program into the SPE context
           if ((data[i].speid = spe_context_create (0, NULL)) == NULL)
           {
                   fprintf (stderr, "Failed spe_context_create(errno=%d strerror=%s)\n", errno, strerror(errno));
                   exit (3);
           }

           //Load program
           if ((spe_program_load (data[i].speid, &spu)) != 0)
           {
                   fprintf (stderr, "Failed spe_program_load(errno=%d strerror=%s)\n", errno, strerror(errno));
                   exit (3);
           }

           //Setup the control block and thread structure
           cb[i].changedPixels = (unsigned int) &changedPixels[(i * MAX_PIXELS)];
           cb[i].originalPixels = (unsigned int) &originalPixels[(i * MAX_PIXELS * 4)];
   }

   for (i = 0; i < numThreads; i++)
   {
           // create SPE pthreads
		   if ((pthread_create (&data[i].pthread, NULL, &ppu_pthread_function, &data[i])) != 0)
           {
                   fprintf (stderr, "Failed pthread_create(errno=%d strerror=%s)\n", errno, strerror(errno));
                   exit (3);
           }

           DoSpeWork(&cb[i]);

           // wait for SPE to complete
           if ((rc = pthread_join (data[i].pthread, NULL)) != 0)
           {
                   fprintf (stderr, "Failed pthread_join(rc=%d, errno=%d strerror=%s)\n", rc, errno, strerror(errno));
                   exit (1);
           }
           fflush(stdout);
   }

    for(i=0;i<numThreads;i++)
    {
            // destroy the SPE contexts
            if ((rc = spe_context_destroy (data[i].speid)) != 0)
            {
                    fprintf (stderr, "Failed spe_context_destroy(rc=%d, errno=%d strerror=%s)\n", rc, errno, strerror(errno));
                    exit (1);
            }
    }

   // Recompose the picture
     char * newPic = (char *)malloc(numPixels * sizeof(PIXEL));
     tmp = newPic;
     for (i=0; i < numPixels; i++)
     {
  	   tmp[0] = changedPixels[i].red;
  	   tmp[1] = changedPixels[i].green;
  	   tmp[2] = changedPixels[i].blue;
  	   tmp += 3;
     }


   // Write the picture back to disk
   const char * fileName = "NewPic.bmp";
   SaveDIBitmap(fileName, ptPictureData, newPic);

  free(newPic);
  printf("end");
exit (0);
}
